﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

using JoyServer.Events;

namespace JoyServer.Net
{
	/// <summary>
	/// TCP サーバに接続するクライアント 1 件分の情報
	/// </summary>
	class ClientConnection : EventDispatcher
	{
		/// <summary>
		/// TCP クライアント
		/// </summary>
		private TcpClient tcpClient;

		/// <summary>
		/// データ受信待ちスレッド
		/// </summary>
		private Thread thread;

		/// <summary>
		/// 停止フラグ
		/// </summary>
		private bool isStop;

		/// <summary>
		/// 接続確認用ストップウォッチ
		/// 1 秒ごとにクライアントに接続確認パケットを送る。
		/// </summary>
		private Stopwatch stopwatch;

		/// <summary>
		/// 接続確認用パケット
		/// </summary>
		private Packet pingPacket;

		/// <summary>
		/// IP アドレスとポートの組
		/// </summary>
		public String endPointString;

		/// <summary>
		/// 組み立て中のパケット
		/// </summary>
		public Packet packet;

		/// <summary>
		/// パケットの組み立てフェーズ
		/// </summary>
		public uint packetPhase;

		/// <summary>
		/// 次の組み立てフェーズで必要となるパケットのバイト数
		/// </summary>
		public uint packetNecessaryBytesLength;


		/// <summary>
		/// コンストラクタ
		/// </summary>
		public ClientConnection()
		{
			this.tcpClient = null;
			this.thread = null;
			this.isStop = false;
			this.stopwatch = new Stopwatch();
			this.pingPacket = PacketBuilder.createPingPacket();
			this.packet = null;
			this.packetPhase = 0;
			this.packetNecessaryBytesLength = 0;
		}

		/// <summary>
		/// クライアントを切断する
		/// </summary>
		/// <param name="context"></param>
		public void close()
		{
			isStop = true;
		}

		/// <summary>
		/// クライアントのデータ受信開始
		/// </summary>
		/// <param name="client"></param>
		public void start(TcpClient client)
		{
			//client.ReceiveTimeout = 1000;
			//client.SendTimeout = 1000;
			
			tcpClient = client;
			endPointString = client.Client.RemoteEndPoint.ToString();

			startReceiveThread();
		}

		/// <summary>
		/// クライアントへデータを送信する
		/// </summary>
		/// <param name="data"></param>
		public void send(ByteArray data)
		{
			// エラー処理
			if (tcpClient == null)
				return;
			if (!tcpClient.Client.Connected)
				return;

			data.position = 0;

			int length = (int)data.length;
			byte[] buffer = data.toBytes();
			
			NetworkStream stream = tcpClient.GetStream();
			AsyncCallback callback = new AsyncCallback(asyncWrite);

			if (!stream.CanWrite)
			{
				isStop = true;
				return;
			}

			try
			{
				// 非ブロッキングモードで送信
				stream.BeginWrite(buffer, 0, buffer.Length, callback, stream);
			}
			catch (IOException)
			{
				close();
				return;
			}
		}

		/// <summary>
		/// クライアントへパケットを送信する
		/// </summary>
		/// <param name="packet"></param>
		public void sendPacket(Packet packet)
		{
			packet.data.position = 0;

			ByteArray data = new ByteArray();
			data.writeShort((short)packet.type);
			data.writeShort((short)packet.data.length);
			data.writeBytes(packet.data, 0, packet.data.length);

			send(data);
		}


		/// <summary>
		/// データ受信用スレッドを開始する
		/// </summary>
		private void startReceiveThread()
		{
			ThreadStart threadStart;
			threadStart = new ThreadStart(dataReceive);
			thread = new Thread(threadStart);
			thread.Start();
		}

		/// <summary>
		/// データ受信完了イベントを発行する
		/// </summary>
		/// <param name="receiveData">受信したデータ</param>
		private void dispatchDataReceiveEvent(ByteArray receiveData)
		{
			ClientEvent e;
			e = new ClientEvent(this, ClientEvent.DATA_RECEIVE);
			e.connection = this;
			e.data = receiveData;
			dispatchEvent(e);
		}

		/// <summary>
		/// クライアント切断イベントを発行する
		/// </summary>
		private void dispatchCloseEvent()
		{
			ClientEvent e;
			e = new ClientEvent(this, ClientEvent.CLOSE);
			e.connection = this;
			dispatchEvent(e);
		}

		/// <summary>
		/// データ送信完了イベントを発行する
		/// </summary>
		private void dispatchSentEvent()
		{
			ClientEvent e;
			e = new ClientEvent(this, ClientEvent.SENT);
			e.connection = this;
			dispatchEvent(e);
		}

		/// <summary>
		/// クライアントからのデータを待つループ
		/// 別スレッドで動作する
		/// </summary>
		/// <param name="param"></param>
		private void dataReceive()
		{
			// エラー処理
			if (tcpClient == null)
				return;
			if (!tcpClient.Connected)
				return;

			NetworkStream stream = tcpClient.GetStream();
			BinaryReader reader = new BinaryReader(stream);

			stopwatch.Start();

			while (!isStop)
			{
				// 接続チェック
				if (!tcpClient.Client.Connected)
					break;


				if (reader.BaseStream.CanRead && tcpClient.Available > 0)
				{
					//Debug.WriteLine("tcpClient.Available: " + tcpClient.Available);
					byte[] buffer = new byte[tcpClient.Available];
					AsyncCallback callback = new AsyncCallback(asyncReceive);
					stream.BeginRead(buffer, 0, buffer.Length, callback, buffer);
				}

				if (stopwatch.ElapsedMilliseconds > 1000)
				{
					sendPacket(pingPacket);
					stopwatch.Reset();
					stopwatch.Start();
				}

				// 1 ミリ秒待つ
				Thread.Sleep(1);
			}
			tcpClient.Close();
			dispatchCloseEvent();
		}

		/// <summary>
		/// 非同期送信完了
		/// NetworkStream.BeginWrite() メソッド用のコールバック関数
		/// </summary>
		/// <param name="result"></param>
		private void asyncWrite(IAsyncResult result)
		{
			if (isStop)
				return;

			NetworkStream stream;
			stream = result.AsyncState as NetworkStream;

			if (stream == null)
				return;

			stream.Flush();
			dispatchSentEvent();
		}

		/// <summary>
		/// 非同期受信完了
		/// NetworkStream.BeginRead() メソッド用のコールバック関数
		/// </summary>
		/// <param name="result"></param>
		private void asyncReceive(IAsyncResult result)
		{
			if (isStop)
				return;

			byte[] buffer;
			buffer = result.AsyncState as byte[];
			
			if (buffer == null)
				return;

			ByteArray receiveData;
			receiveData = new ByteArray();
			receiveData.writeBytes(buffer);
			receiveData.position = 0;

			dispatchDataReceiveEvent(receiveData);
		}
	}
}
